<?php

declare(strict_types=1);
namespace App\Auth\Guard;

use Hyperf\Context\Context;
use Hyperf\HttpServer\Contract\RequestInterface;
use App\Auth\AdminJWTManager;
use Qbhy\HyperfAuth\Authenticatable;
use Qbhy\HyperfAuth\Exception\AuthException;
use Qbhy\HyperfAuth\Exception\UnauthorizedException;
use Qbhy\HyperfAuth\Guard\JwtGuard;
use Qbhy\HyperfAuth\UserProvider;
use Qbhy\SimpleJwt\Exceptions\TokenBlacklistException;
use Qbhy\SimpleJwt\Exceptions\TokenExpiredException;
use Qbhy\SimpleJwt\Exceptions\TokenNotActiveException;

class AdminJwtGuard extends JwtGuard
{
    /**
     * AdminJwtGuard constructor.
     * @param array $config
     * @param string $name
     * @param UserProvider $userProvider
     * @param RequestInterface $request
     */
    public function __construct(array $config, string $name, UserProvider $userProvider, RequestInterface $request)
    {
        parent::__construct($config, $name, $userProvider, $request);
        $this->jwtManager = new AdminJWTManager($config);
    }

    /**
     * @param Authenticatable $user
     * @param array $payload
     * @return string
     */
    public function login(Authenticatable $user, array $payload = [])
    {
        $token = $this->getJwtManager()->make(array_merge($payload, [
            'uid' => $user->getId(),
            's' => str_random(),
            'ip' => $this->request->getAttribute('client-ip'),
            'is_credible' => in_array($user->username, config('admin.auth.credible_account')),
        ]))->token();

        Context::set($this->resultKey($token), $user);

        return $token;
    }

    /**
     * @param string|null $token
     * @return Authenticatable|null
     * @throws \Throwable
     */
    public function user(?string $token = null): ?Authenticatable
    {
        $token = $token ?? $this->parseToken();
        if (Context::has($key = is_string($token) ? $this->resultKey($token) : '_nothing')) {
            $result = Context::get($key);
            if ($result instanceof UnauthorizedException) {
                throw $result;
            }
            return $result ?: null;
        }

        try {
            if ($token) {
                $jwt = $this->getJwtManager()->parse($token);
                $payload = $jwt->getPayload();
                $is_credible = $payload['is_credible'] ?? false;

                if (!$is_credible && config('admin.auth.ip_guard', false) && $this->request->getAttribute('client-ip') !== $payload['ip']) {
                    throw new UnauthorizedException('The token and ip is not match.', $this);
                }
                $uid = $payload['uid'] ?? null;
                $user = $uid ? $this->userProvider->retrieveByCredentials($uid) : null;
                Context::set($key, $user ?: 0);

                return $user;
            }

            throw new UnauthorizedException('The token is required.', $this);
        } catch (\Throwable $exception) {
            $newException = $exception instanceof AuthException ? $exception : new UnauthorizedException(
                $exception->getMessage(),
                $this,
                $exception
            );
            Context::set($key, $newException);
            throw $newException;
        }
    }
}
